using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using LiteNetLib.Utils;

namespace LiteNetLib
{
	public sealed class NatPunchModule
	{
		private struct RequestEventData
		{
			public IPEndPoint LocalEndPoint;

			public IPEndPoint RemoteEndPoint;

			public string Token;
		}

		private struct SuccessEventData
		{
			public IPEndPoint TargetEndPoint;

			public string Token;
		}

		private readonly NetSocket _socket;

		private readonly Queue<RequestEventData> _requestEvents;

		private readonly Queue<SuccessEventData> _successEvents;

		private const byte HostByte = 1;

		private const byte ClientByte = 0;

		public const int MaxTokenLength = 256;

		private INatPunchListener _natPunchListener;

		internal NatPunchModule(NetSocket socket)
		{
			_socket = socket;
			_requestEvents = new Queue<RequestEventData>();
			_successEvents = new Queue<SuccessEventData>();
		}

		public void Init(INatPunchListener listener)
		{
			_natPunchListener = listener;
		}

		public void NatIntroduce(IPEndPoint hostInternal, IPEndPoint hostExternal, IPEndPoint clientInternal, IPEndPoint clientExternal, string additionalInfo)
		{
			NetDataWriter netDataWriter = new NetDataWriter();
			netDataWriter.Put((byte)13);
			netDataWriter.Put((byte)0);
			netDataWriter.Put(hostInternal);
			netDataWriter.Put(hostExternal);
			netDataWriter.Put(additionalInfo, 256);
			SocketError errorCode = SocketError.Success;
			_socket.SendTo(netDataWriter.Data, 0, netDataWriter.Length, clientExternal, ref errorCode);
			netDataWriter.Reset();
			netDataWriter.Put((byte)13);
			netDataWriter.Put((byte)1);
			netDataWriter.Put(clientInternal);
			netDataWriter.Put(clientExternal);
			netDataWriter.Put(additionalInfo, 256);
			_socket.SendTo(netDataWriter.Data, 0, netDataWriter.Length, hostExternal, ref errorCode);
		}

		public void PollEvents()
		{
			if (_natPunchListener == null)
			{
				return;
			}
			lock (_successEvents)
			{
				while (_successEvents.Count > 0)
				{
					SuccessEventData successEventData = _successEvents.Dequeue();
					_natPunchListener.OnNatIntroductionSuccess(successEventData.TargetEndPoint, successEventData.Token);
				}
			}
			lock (_requestEvents)
			{
				while (_requestEvents.Count > 0)
				{
					RequestEventData requestEventData = _requestEvents.Dequeue();
					_natPunchListener.OnNatIntroductionRequest(requestEventData.LocalEndPoint, requestEventData.RemoteEndPoint, requestEventData.Token);
				}
			}
		}

		public void SendNatIntroduceRequest(IPEndPoint masterServerEndPoint, string additionalInfo)
		{
			NetDataWriter netDataWriter = new NetDataWriter();
			string localIp = NetUtils.GetLocalIp(LocalAddrType.IPv4);
			if (string.IsNullOrEmpty(localIp))
			{
				localIp = NetUtils.GetLocalIp(LocalAddrType.IPv6);
			}
			IPEndPoint endPoint = NetUtils.MakeEndPoint(localIp, _socket.LocalPort);
			netDataWriter.Put((byte)12);
			netDataWriter.Put(endPoint);
			netDataWriter.Put(additionalInfo, 256);
			SocketError errorCode = SocketError.Success;
			_socket.SendTo(netDataWriter.Data, 0, netDataWriter.Length, masterServerEndPoint, ref errorCode);
		}

		private void HandleNatPunch(IPEndPoint senderEndPoint, NetDataReader dr)
		{
			byte @byte = dr.GetByte();
			if (@byte != 1 && @byte != 0)
			{
				return;
			}
			string @string = dr.GetString(256);
			lock (_successEvents)
			{
				Queue<SuccessEventData> successEvents = _successEvents;
				SuccessEventData item = default(SuccessEventData);
				item.TargetEndPoint = senderEndPoint;
				item.Token = @string;
				successEvents.Enqueue(item);
			}
		}

		private void HandleNatIntroduction(NetDataReader dr)
		{
			byte @byte = dr.GetByte();
			IPEndPoint netEndPoint = dr.GetNetEndPoint();
			IPEndPoint netEndPoint2 = dr.GetNetEndPoint();
			string @string = dr.GetString(256);
			NetDataWriter netDataWriter = new NetDataWriter();
			netDataWriter.Put((byte)14);
			netDataWriter.Put(@byte);
			netDataWriter.Put(@string);
			SocketError errorCode = SocketError.Success;
			_socket.SendTo(netDataWriter.Data, 0, netDataWriter.Length, netEndPoint, ref errorCode);
			netDataWriter.Reset();
			netDataWriter.Put((byte)14);
			netDataWriter.Put(@byte);
			netDataWriter.Put(@string);
			if (@byte == 1)
			{
				_socket.Ttl = 2;
				_socket.SendTo(netDataWriter.Data, 0, netDataWriter.Length, netEndPoint2, ref errorCode);
				_socket.Ttl = 255;
			}
			else
			{
				_socket.SendTo(netDataWriter.Data, 0, netDataWriter.Length, netEndPoint2, ref errorCode);
			}
		}

		private void HandleNatIntroductionRequest(IPEndPoint senderEndPoint, NetDataReader dr)
		{
			IPEndPoint netEndPoint = dr.GetNetEndPoint();
			string @string = dr.GetString(256);
			lock (_requestEvents)
			{
				Queue<RequestEventData> requestEvents = _requestEvents;
				RequestEventData item = default(RequestEventData);
				item.LocalEndPoint = netEndPoint;
				item.RemoteEndPoint = senderEndPoint;
				item.Token = @string;
				requestEvents.Enqueue(item);
			}
		}

		internal void ProcessMessage(IPEndPoint senderEndPoint, NetPacket packet)
		{
			NetDataReader dr = new NetDataReader(packet.RawData, 1, packet.Size);
			switch (packet.Property)
			{
			case PacketProperty.NatIntroductionRequest:
				HandleNatIntroductionRequest(senderEndPoint, dr);
				break;
			case PacketProperty.NatIntroduction:
				HandleNatIntroduction(dr);
				break;
			case PacketProperty.NatPunchMessage:
				HandleNatPunch(senderEndPoint, dr);
				break;
			}
		}
	}
}
